Jerry's Log

Service Discovery

contents

서비스 디스커버리는 msa 내의 서비스들이 잘 통신할 수 있도록 도와주는 기술입니다. 이 기술이 없다면 마이크로서비스들은 연락처 목록도 없이 서로에게 전화를 걸려고 하는 사람들과 같습니다. 매번 바뀌는 전화번호(IP 주소)를 전부 외우고 있어야 하기 때문이죠.

다음은 서비스 디스커버리의 개념, 패턴, 그리고 Spring Cloud와 Kubernetes에서의 작동 방식에 대한 분석입니다.


1. 핵심 문제: "당신, 어디에 있나요?"

옛날(모놀리스 시절)에는 애플리케이션을 고정된 서버에 호스팅했습니다. IP 주소가 192.168.1.50이라는 것을 알았고, 이를 설정 파일에 하드코딩할 수 있었습니다.

하지만 클라우드/마이크로서비스 시대에는 모든 것이 동적입니다.

  1. 오토 스케일링 (Auto-scaling): 트래픽이 폭주하면 OrderService 인스턴스 5개가 갑자기 새로 생겨납니다.
  2. 장애 (Failures): 인스턴스가 죽으면 다른 IP 주소를 가진 새 인스턴스로 대체됩니다.
  3. 결과: 더 이상 IP 주소를 하드코딩할 수 없습니다. "PaymentService의 현재 IP 주소가 뭐지?"를 실시간으로 알아낼 방법이 필요합니다.

2. 해결책: "전화번호부" (서비스 레지스트리)

서비스 디스커버리는 서비스 레지스트리(Service Registry) 라는 중앙 데이터베이스를 도입합니다.


3. 두 가지 주요 패턴

이것을 구현하는 데는 두 가지 방법이 있습니다. 시스템 설계 인터뷰에서 자주 묻는 내용입니다.

패턴 A: 클라이언트 사이드 디스커버리 (예: Netflix Eureka)

"클라이언트"(호출하는 쪽 마이크로서비스)가 직접 레지스트리에게 목적지의 위치를 물어보는 방식입니다.

  1. 1단계: OrderService(클라이언트)가 서비스 레지스트리(Eureka)에게 묻습니다. "PaymentService의 IP 목록 좀 줘."
  2. 2단계: 레지스트리는 목록을 반환합니다: [10.0.0.1, 10.0.0.2].
  3. 3단계: OrderService는 클라이언트 측 로드 밸런서(Spring Cloud LoadBalancer 등)를 사용하여 IP 하나를 고르고 직접 호출합니다.

패턴 B: 서버 사이드 디스커버리 (예: Kubernetes, AWS ELB)

클라이언트는 로드 밸런서(LB)를 호출하고, LB가 레지스트리와 통신합니다. 클라이언트는 디스커버리 로직에 대해 아무것도 모릅니다.

  1. 1단계: OrderService는 일반적인 DNS 이름으로 요청을 보냅니다: http://payment-service.
  2. 2단계: 요청이 로드 밸런서(Kube-Proxy나 AWS ELB)에 도달합니다.
  3. 3단계: LB가 서비스 레지스트리를 조회하여 인스턴스를 하나 고르고 트래픽을 전달합니다.

4. 마이크로서비스의 생명주기

데이터는 어떻게 레지스트리에 들어갈까요?

  1. 자가 등록 (Self-Registration, 시작): PaymentService가 켜지면 레지스트리에 REST 요청을 보냅니다: "안녕, 나 PaymentService야. 내 IP는 10.0.0.5고, 지금 가동됐어."
  2. 하트비트 (Heartbeat, 맥박): 30초마다(설정 가능) PaymentService가 레지스트리에 신호를 보냅니다: "나 아직 살아있어."
  1. 디스커버리 (Discovery, 조회): 다른 서비스들이 레지스트리를 조회해서 최신 목록을 가져갑니다.
  2. 등록 해제 (De-registration, 종료): 서비스가 정상 종료될 때 "잘 있어(Goodbye)" 메시지를 보내면, 레지스트리는 즉시 목록에서 제거합니다.

5. 주요 도구 비교

도구 유형 일관성 모델 헬스 체크 내장? Spring 지원
Netflix Eureka AP (일관성보다 가용성 우선) 강함 예 (하트비트) 🏆 최우수
Consul (HashiCorp) CP (가용성보다 일관성 우선) 강함 예 (능동적 탐지) 좋음
Zookeeper CP 강함 아니오 (확장 필요) 좋음 (구식)
Kubernetes (ETCD) 서버 사이드 (DNS) 강함 예 (Kubelet) 네이티브

6. Spring Cloud 구현 (Eureka)

Spring 생태계(비-쿠버네티스 환경)에서는 Eureka가 표준입니다.

1. 서버 (@EnableEurekaServer) 독립적인 Spring Boot 앱을 하나 만들어서 레지스트리 역할을 시킵니다.

@SpringBootApplication
@EnableEurekaServer // 이 앱을 레지스트리로 변신시킴
public class DiscoveryServerApplication { ... }

2. 클라이언트 (@EnableDiscoveryClient) 각 마이크로서비스(pom.xml)에 추가합니다:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

설정 (application.yml):

eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/ # 서버 위치 지정

이제 RestTemplate이나 WebClienthttp://localhost:8081을 호출하는 대신, http://PAYMENT-SERVICE/api/pay 처럼 서비스 이름으로 호출할 수 있습니다.

7. 현대적 변화: Kubernetes (쿠버네티스)

만약 쿠버네티스(K8s) 환경에 배포한다면, 보통은 Eureka를 사용하지 않습니다. K8s는 서비스 디스커버리가 자체 내장되어 있기 때문입니다.

K8s에서는 Service 객체를 정의하면 K8s가 DNS 엔트리를 할당해 줍니다. 자바 코드에서는 단순히 http://payment-service라고 호출하면, K8s의 내부 DNS가 알아서 올바른 내부 IP로 연결해 줍니다. 자바 코드에서 유레카 관련 복잡성을 덜어낼 수 있어 현대적인 모범 사례(Best Practice)로 꼽힙니다.

references